Skip to content

feat: add normalizePluginMetadata() for map→array and alias resolution#686

Merged
hotlong merged 2 commits intomainfrom
copilot/fix-downstream-project-issue
Feb 15, 2026
Merged

feat: add normalizePluginMetadata() for map→array and alias resolution#686
hotlong merged 2 commits intomainfrom
copilot/fix-downstream-project-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 15, 2026

Downstream projects (e.g. studio) must patch plugin metadata normalization at boot because the runtime only handles dual-format (map/array) for objects, not for actions, workflows, hooks, etc. Plugins commonly define these as maps and use legacy field names like triggers instead of hooks.

Changes

  • normalizePluginMetadata() — new utility that:
    • Converts map-formatted metadata collections to arrays for all MAP_SUPPORTED_FIELDS
    • Resolves field aliases via METADATA_ALIASES (currently triggershooks), merging into existing canonical values
    • Recursively normalizes nested plugins
  • METADATA_ALIASES — extensible Record<string, MapSupportedField> for legacy field name mappings
  • Both exported from @objectstack/spec root and Shared namespace
  • 13 new tests covering conversion, aliasing, merging, recursion, and edge cases

Usage

import { normalizePluginMetadata } from '@objectstack/spec';

const normalized = normalizePluginMetadata({
  actions: { lead_convert: { type: 'custom', label: 'Convert' } },
  triggers: { lead_scoring: { object: 'lead', event: 'afterInsert' } },
});
// normalized.actions → [{ name: 'lead_convert', type: 'custom', label: 'Convert' }]
// normalized.hooks   → [{ name: 'lead_scoring', object: 'lead', event: 'afterInsert' }]
// normalized.triggers → undefined (merged into hooks)
Original prompt

解决下游项目的这个问题,目前需要打补丁

拉取请求: https://github.com/objectstack-ai/studio/pull/94


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectstack-play Ready Ready Preview, Comment Feb 15, 2026 4:02am
spec Ready Ready Preview, Comment Feb 15, 2026 4:02am

Request Review

…collection utilities

Adds a normalizePluginMetadata() function that converts map-formatted
plugin metadata to arrays, resolves field aliases (triggers → hooks),
and recursively normalizes nested plugins. This eliminates the need for
downstream projects (e.g. studio) to patch this normalization themselves.

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix issue in downstream project requiring patch feat: add normalizePluginMetadata() for map→array and alias resolution Feb 15, 2026
Copilot AI requested a review from hotlong February 15, 2026 03:21
@hotlong hotlong marked this pull request as ready for review February 15, 2026 03:40
Copilot AI review requested due to automatic review settings February 15, 2026 03:40
@hotlong hotlong merged commit f5b393d into main Feb 15, 2026
1 of 3 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds normalizePluginMetadata() to handle map-to-array conversion and legacy field name resolution for plugin metadata, solving a patching issue in downstream projects like studio. The runtime currently only handles dual-format support (map/array) for top-level stack definitions via normalizeStackInput(), but plugins need similar normalization for their own metadata collections.

Changes:

  • Adds normalizePluginMetadata() function to convert map-formatted metadata to arrays, resolve field aliases (e.g., triggershooks), and recursively normalize nested plugins
  • Adds METADATA_ALIASES constant to map legacy field names to canonical names
  • Exports both utilities from @objectstack/spec root and Shared namespace
  • Includes 13 comprehensive tests covering conversion, aliasing, merging, recursion, and edge cases

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/spec/src/shared/metadata-collection.zod.ts Implements METADATA_ALIASES constant and normalizePluginMetadata() function with three-step normalization: alias resolution with merging, map→array conversion, and recursive nested plugin handling
packages/spec/src/shared/metadata-collection.test.ts Adds comprehensive test suite with 13 tests covering map→array conversion, alias resolution (triggers→hooks), recursive normalization, and edge cases
packages/spec/src/index.ts Exports normalizePluginMetadata and METADATA_ALIASES from root package (also available via Shared namespace)

Comment on lines +220 to +242
// 1. Resolve aliases (e.g. triggers → hooks), merging with any existing canonical values
for (const [alias, canonical] of Object.entries(METADATA_ALIASES)) {
if (alias in result) {
const aliasValue = normalizeMetadataCollection(result[alias]);
const canonicalValue = normalizeMetadataCollection(result[canonical]);

// Merge: canonical array wins; alias values are appended
if (Array.isArray(aliasValue)) {
(result as Record<string, unknown>)[canonical] = Array.isArray(canonicalValue)
? [...canonicalValue, ...aliasValue]
: aliasValue;
}

delete (result as Record<string, unknown>)[alias];
}
}

// 2. Normalize map-formatted collections → arrays
for (const field of MAP_SUPPORTED_FIELDS) {
if (field in result) {
(result as Record<string, unknown>)[field] = normalizeMetadataCollection(result[field]);
}
}
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation normalizes collections twice in certain cases. When an alias field exists (e.g., triggers), both the alias value (line 223) and the canonical value (line 224) are normalized. Then, all MAP_SUPPORTED_FIELDS including the canonical field are normalized again in step 2 (line 240). While normalizeMetadataCollection() is idempotent so this doesn't cause incorrect results, it does perform unnecessary work.

Consider optimizing by tracking which fields have already been normalized in step 1, or by skipping already-normalized canonical fields in step 2. For example, you could collect canonical field names from METADATA_ALIASES and skip them when iterating through MAP_SUPPORTED_FIELDS.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants